#ifndef J4310_H
#define J4310_H
#include <cstdint>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "../src/Logger.h"
#include <linux/can.h>

#include "CanDriver.h"
#include "../../3rdparty/canpp/3rdparty/Ring-Buffer/ringbuffer.hpp"

namespace MM {
    using std::string;
    using std::array, std::vector, std::string;
    using std::unique_ptr;
    using std::optional, std::nullopt;
    using std::function;
    using bq::log;

    enum DMStatus {
        Disabled = 0,
        Enabled = 1,
        OverVoltage = 8,
        UnderVoltage = 9,
        OverCurrent = 0xA,
        MosOverheat = 0xB,
        RotorOverheat = 0xC,
        Disconnected = 0xD,
        Overloaded = 0xE,
    };

    struct J4310ControlFrame {
        virtual ~J4310ControlFrame() = default;

        virtual explicit operator can_frame() = 0;

        virtual string format() = 0;

        optional<uint16_t> frameId = nullopt;
    };

    struct J4310Status {
        J4310Status() = default;

        explicit J4310Status(const can_frame &frame);

        string format();

        uint8_t ID;
        DMStatus status;
        float position;
        float velocity;
        float torque;
        uint8_t temperature_MOS;
        uint8_t temperature_Rotor;
    };

    struct J4310MITControl final : J4310ControlFrame {
        explicit operator can_frame() override;

        string format() override;

        J4310MITControl(const float position, const float velocity, const float torque, const float kp, const float kd,
                        const optional<uint16_t> &&canId = nullopt)
            : position(position),
              velocity(velocity),
              torque(torque),
              Kp(kp),
              Kd(kd) {
            frameId = canId;
        }

        float position;
        float velocity;
        float torque;
        float Kp;
        float Kd;
    };

    struct J4310PosVelControl final : J4310ControlFrame {
        explicit operator can_frame() override;

        string format() override;

        J4310PosVelControl(const float position, const float velocity, const optional<uint16_t> &&canId = nullopt)
            : position(position),
              velocity(velocity) {
            frameId = canId;
        }

        float position;
        float velocity;
    };

    struct J4310VelControl final : J4310ControlFrame {
        explicit operator can_frame() override;

        string format() override;

        explicit J4310VelControl(const float velocity, const optional<uint16_t> &&canId = nullopt)
            : velocity(velocity) {
            frameId = canId;
        }

        float velocity;
    };

    struct J4310Enable final : J4310ControlFrame {
        explicit operator can_frame() override;

        string format() override;

        J4310Enable() = default;

        explicit J4310Enable(const optional<uint16_t> &&canId) {
            frameId = canId;
        };
    };

    struct J4310Disable final : J4310ControlFrame {
        explicit operator can_frame() override;

        string format() override;

        J4310Disable() = default;

        explicit J4310Disable(const optional<uint16_t> &&canId) {
            frameId = canId;
        };
    };

    struct J4310ZeroPosition final : J4310ControlFrame {
        explicit operator can_frame() override;

        string format() override;

        J4310ZeroPosition() = default;

        explicit J4310ZeroPosition(const optional<uint16_t> &&canId) {
            frameId = canId;
        };
    };

    struct J4310CleanError final : J4310ControlFrame {
        explicit operator can_frame() override;

        string format() override;

        J4310CleanError() = default;

        explicit J4310CleanError(const optional<uint16_t> &&canId) {
            frameId = canId;
        };
    };

    class J4310 {
    public:
        J4310() = delete;

        ~J4310();

        explicit J4310(string canInterface, uint16_t canId = 0x01, uint16_t masterId = 0x00,
                       optional<string> &&loggerId = nullopt,
                       bool loggingToTextfile = true,
                       bool loggingToConsole = false);

        void control(J4310ControlFrame &&frame) const noexcept;

    private:
        static void fuse_check(const J4310Status &status);

        jnk0le::Ringbuffer<J4310Status, 1024> statuses{};
        string interface{};
        string id{};
        uint16_t canId{};
        uint16_t masterId{};
        log logger;
        unique_ptr<canpp::CanDriver<> > can_driver = nullptr;
    };
}

#endif //J4310_H
